import math
import pickle
import random
import time
import sys
import Define
from Environment import Environment
from Genome import Genome


def simplify(expression):
    expression = expression.replace('log', 'ln')

    print(expression)


def openreadtxt(file_name):
    inputs = []
    outputs = []
    file = open(file_name, 'r')  # 打开文件
    file_data = file.readlines()  # 读取所有行
    for row in file_data:
        tmp_list = row.split(', ')  # 按' '切分每行的数据
        tmp_list[-1] = tmp_list[-1].replace('\n', '')  # 去掉换行符
        inputs.append([float(tmp_list[0])])
        outputs.append([float(tmp_list[1])])
    return inputs, outputs, len(file_data)


def generateLength(headLength, RIS_flag):
    lengthList = []
    for i in range(headLength):
        lengthList.append(i + 1)
    if RIS_flag:
        lengthList.remove(headLength)
    return lengthList


def evalFunction(C_value, T_value, maxFitness_flag=False, M=100, DataCount=10, absoluteError_flag=True):
    '''
    评估函数,返回fitness值
    :param C_value:                 表示染色体,样本输出值
    :param T_value:                 表示染色体,样本真实值
    :param maxFitness_flag:         True 返回为最大适应度值
    :param M:                       选择范围 M 常数
    :param DataCount:
    :param absoluteError_flag:
    :return:
    '''
    '''
    :param C_value:         表示染色体,样本输出值
    :param T_value:         表示染色体,样本真实值
    :param maxFitness_flag: 用于是否为最大适应度值
    :param M:               M 常数
    :param DataCount:       数据的个数
    :return:
    '''
    if maxFitness_flag:
        return M * DataCount
    sum_fitness = 0
    for i in range(len(C_value)):
        if not math.isfinite(C_value[i][0]):
            sum_fitness += 0
            pass
        if absoluteError_flag:
            result = M - abs(C_value[i][0] - T_value[i][0])
            if result < 0: result = 0
            sum_fitness = result + sum_fitness
        else:
            result = (M - abs((C_value[i][0] - T_value[i][0]) * 100 / T_value[i][0]))
            if result < 0: result = 0
            sum_fitness = result + sum_fitness
    fitness = sum_fitness
    return fitness


def train(generation_count, population_size, num_genes, head_length, mutation_rate, ISTransposition_rate,
          RISTransposition_rate, geneTransposition_rate, onePointRecombination_rate, twoPointRecombination_rate,
          geneRecombination_rate, rouletteFlag, absoluteErrorFlag, homeoticHead_length, homeotic_rate, model_name, model_path):
    inputs, outputs, data_len = openreadtxt(model_path)
    inputsOutputs = (inputs, outputs)

    Define.SEED = 6
    random.seed(Define.SEED)  # 设置随机种子
    # Define.SEED = 1
    startTime = time.perf_counter()
    # —————————————————参数设置 Begin————————————————#
    generationCount = generation_count  # 迭代的代数
    populationSize = population_size  # 一个群染色体的个数
    DataCount = 30  # 样本数据的个数
    headLength = head_length  # 基因的头部长度    （1）
    numGenes = num_genes  # 一个染色体中基因的个数    （1）
    M = 100  # 选择访问M常数
    absoluteError_flag = absoluteErrorFlag  # True 是绝对误差，False是相对误差

    # —————————————————连接基因 start—————————————————#
    # 连接基因即同源异型基因，当有多个同源异型基因时，一个染色体内有多个细胞，可以有多个输出。
    # 也可以挑选表现得比较好的一个细胞作为该染色体的代表，只输出一个。
    homeotic_flag = True  # 是否有连接基因,False:用加法进行连接,True
    # if j == 1:
    #     numHomeotics = 1 # 一个染色体中连接基因的个数    （1）
    # else:
    #     numHomeotics = j-1 # 一个染色体中连接基因的个数    （1）
    numHomeotics = 1  # 一个染色体中连接基因的个数    （1）
    homeoticHeadLength = homeoticHead_length  # 连接基因的头部长度    （1）
    homeoticRate = homeotic_rate  # 连接基因的变异率

    # —————————————————DC域参数 start—————————————————#
    DC_flag = False  # 是否有DC域
    numRandom = 10  # 随机数的个数
    RandomRangeStart = -1  # 随机数区间起点
    RandomRangeEnd = 1  # 随机数区间终点
    RandomPRECISION = 15  # 随机数精确度(保留几位小数)
    randomSetRate = 0.01  # 随机数突变率
    DcISTranspositionRate = 0.1  # DC域特殊的IS转座率
    DcISElementLength = [1, 2, 3]  # DC域特殊的IS元素长度

    # —————————————————中性基因start—————————————————#
    neutral_flag = True
    TriggerFitnessCount = 1000  # （1）
    neutralValue = 0

    # —————————————————变异概率参数 start—————————————————#

    mutationRate = mutation_rate  # 突变率
    ISTranspositionRate = ISTransposition_rate  # IS转座率
    ISElementLength = generateLength(headLength, False)  # IS元素长度
    RISTranspositionRate = RISTransposition_rate  # RIS转座率
    RISElementLength = generateLength(headLength, True)  # RIS元素长度
    geneTranspositionRate = geneTransposition_rate  # 基因转座率
    onePointRecombinationRate = onePointRecombination_rate  # 单点重组率
    twoPointRecombinationRate = twoPointRecombination_rate  # 两点重组率
    geneRecombinationRate = geneRecombination_rate  # 基因重组率

    # —————————————————函数、终结符定义 start—————————————————#
    roulette_flag = rouletteFlag  # True 赌盘，False 锦标赛
    num_terminators = 1  # 终结符的个数
    all_terminators = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k']
    terminals = all_terminators[0:num_terminators]

    # 有DC域加入 “？” 终结符
    if DC_flag:
        terminals.append('?')
    genome = Genome()
    genome.functions.update(Genome.arithmetic_set)  # 基因的函数集合
    genome.terminals = terminals  # 基因的终结符
    link_genome = Genome()
    link_genome.functions.update(Genome.linker_set)  # 连接基因的函数集合
    # —————————————————参数设置 End—————————————————#

    # —————————————————种群初始化 Begin—————————————————#
    environment = Environment()
    environment.setRates(homeoticRate=homeoticRate, mutationRate=mutationRate, ISTranspositionRate=ISTranspositionRate,
                         ISElementLength=ISElementLength, RISTranspositionRate=RISTranspositionRate,
                         RISElementLength=RISElementLength,
                         geneTranspositionRate=geneTranspositionRate,
                         onePointRecombinationRate=onePointRecombinationRate,
                         twoPointRecombinationRate=twoPointRecombinationRate,
                         geneRecombinationRate=geneRecombinationRate, randomSetRate=randomSetRate,
                         DcISTranspositionRate=DcISTranspositionRate,
                         DcISElementLength=DcISElementLength, numRandom=numRandom, RandomRangeStart=RandomRangeStart,
                         RandomRangeEnd=RandomRangeEnd,
                         RandomPRECISION=RandomPRECISION, TriggerFitnessCount=TriggerFitnessCount,
                         neutralValue=neutralValue)
    environment.init(populationSize=populationSize, numGenes=numGenes, numHomeotics=numHomeotics,
                     headLength=headLength, homeoticHeadLength=homeoticHeadLength, genome=genome,
                     link_genome=link_genome, homeotic_flag=homeotic_flag, DC_flag=DC_flag, neutral_flag=neutral_flag
                     )
    ##—————————————————输出测试—————————————————##
    # —————————————————种群初始化 end—————————————————#

    ##—————————————————输出测试—————————————————##

    result = environment.run(inputsOutputs, evalFunction, generationCount=generationCount, M=M, DataCount=DataCount,
                             absoluteError_flag=absoluteError_flag, roulette_flag=roulette_flag)

    # 最佳适应度
    inputs, outputs, data_len = openreadtxt(model_path)
    inputsOutputs = (inputs, outputs)
    C_valueList = []
    DataCount = data_len  # 样本数据的个数
    M = 100  # 选择访问M常数
    absoluteError_flag = absoluteErrorFlag  # True 是绝对误差，False是相对误差
    # 计算每个染色体对应的 C_value
    # print(inputsOutputs[0])
    for inputs in inputsOutputs[0]:
        C_vlaue = result.eval(inputs)
        C_valueList.append(C_vlaue)
    # evalFunction 进行适应度评估
    fitness = evalFunction(C_valueList, inputsOutputs[1], maxFitness_flag=False, M=M, DataCount=DataCount,
                           absoluteError_flag=absoluteError_flag)
    print(fitness)

    # 函数表达式
    simplify(str(result.simplifyChromosome()))

    # 均方根误差
    inputs, outputs, data_len = openreadtxt(model_path)
    predict = []
    for x in inputs:
        predict.append(result.eval(x)[0])

    # for i in range(len(outputs)):
    #     print('预测结果：' + str(predict[i]) + ',正确结果：' + str(outputs[i][0]))

    # 7、利用测试集检测拟合曲线的误差J(a0,a1,a2)=(a0+a1*father0+a2*mother0)**2+...+(a0+a1*fathern+a2*mothern)**2
    J = 0
    for i in range(len(outputs)):
        J += (predict[i] - outputs[i][0]) ** 2  # x_test[i][0]代表fathern，x_test[i][1]代表mothern
    print(str(J))

    # 拟合曲线的excel文件或者txt文件的绝对路径
    temp = model_name.split('\\\\')
    temp[-1] = temp[-1] + '_fit.txt'
    model_fit_path = ''
    for i in temp:
        model_fit_path += i + '\\'
    model_fit_path = model_fit_path[:-1]
    inputs, outputs, data_len = openreadtxt(model_path)
    f = open(model_fit_path, 'w')  # 这里前面的路径改成项目的路径
    for i in range(len(inputs)):
        f.write(str(round(inputs[i][0], 6)) + ', ' + str(round(outputs[i][0], 6))+ ', ' + str(round(predict[i], 6)) + '\n')
    print(model_fit_path)
    f.close()

    # 拟合模型的绝对路径
    print(model_name + ".pkl")

    # 保存模型
    output_hal = open(model_name + ".pkl", 'wb')
    str1 = pickle.dumps(result)
    output_hal.write(str1)
    output_hal.close()




if __name__ == '__main__':
    a = []
    for i in range(1, len(sys.argv)):
        if i <= 4 or i == 14:
            a.append(int(sys.argv[i]))
        elif i == 12 or i == 13:
            if sys.argv[i] == '相对误差' or sys.argv[i] == '锦标赛':
                a.append(False)
            else:
                a.append(True)
        elif i == 16 or i == 17:
            a.append(sys.argv[i])
        else:
            a.append(float(sys.argv[i]))
    train(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], a[16])
